﻿using CG5_KT.Primitives;
using System;

namespace CG5_KT.Scene
{
    public class MeshFactory
    {
        public static Mesh ConstructCube()
        {
            // Vertices
            Vector3 vFTL = new Vector3(-1, 1, 1);  // Front Top    Left
            Vector3 vFTR = new Vector3(1, 1, 1);   // Front Top    Right
            Vector3 vFBR = new Vector3(1, -1, 1);  // Front Bottom Right
            Vector3 vFBL = new Vector3(-1, -1, 1); // Front Bottom Left

            Vector3 vBTL = new Vector3(-1, 1, -1);  // Back Top    Left
            Vector3 vBTR = new Vector3(1, 1, -1);   // Back Top    Right
            Vector3 vBBR = new Vector3(1, -1, -1);  // Back Bottom Right
            Vector3 vBBL = new Vector3(-1, -1, -1); // Back Bottom Left

            // Normals
            Vector3 nFront = new Vector3(0, 0, 1);
            Vector3 nBack = new Vector3(0, 0, -1);
            Vector3 nLeft = new Vector3(-1, 0, 0);
            Vector3 nRight = new Vector3(1, 0, 0);
            Vector3 nTop = new Vector3(0, 1, 0);
            Vector3 nBottom = new Vector3(0, -1, 0);

            Mesh cubeMesh = new Mesh("Cube", 24, 12);

            // Front Wall
            cubeMesh.Vertices[0] = new Vertex { Coordinates = vFTL, Normal = nFront };
            cubeMesh.Vertices[1] = new Vertex { Coordinates = vFTR, Normal = nFront };
            cubeMesh.Vertices[2] = new Vertex { Coordinates = vFBR, Normal = nFront };
            cubeMesh.Vertices[3] = new Vertex { Coordinates = vFBL, Normal = nFront };

            // Right Wall
            cubeMesh.Vertices[4] = new Vertex { Coordinates = vFTR, Normal = nRight };
            cubeMesh.Vertices[5] = new Vertex { Coordinates = vBTR, Normal = nRight };
            cubeMesh.Vertices[6] = new Vertex { Coordinates = vBBR, Normal = nRight };
            cubeMesh.Vertices[7] = new Vertex { Coordinates = vFBR, Normal = nRight };

            // Back Wall
            cubeMesh.Vertices[8] = new Vertex { Coordinates = vBTL, Normal = nBack };
            cubeMesh.Vertices[9] = new Vertex { Coordinates = vBTR, Normal = nBack };
            cubeMesh.Vertices[10] = new Vertex { Coordinates = vBBR, Normal = nBack };
            cubeMesh.Vertices[11] = new Vertex { Coordinates = vBBL, Normal = nBack };

            // Left Wall
            cubeMesh.Vertices[12] = new Vertex { Coordinates = vBTL, Normal = nLeft };
            cubeMesh.Vertices[13] = new Vertex { Coordinates = vFTL, Normal = nLeft };
            cubeMesh.Vertices[14] = new Vertex { Coordinates = vFBL, Normal = nLeft };
            cubeMesh.Vertices[15] = new Vertex { Coordinates = vBBL, Normal = nLeft };

            // Bottom Wall
            cubeMesh.Vertices[16] = new Vertex { Coordinates = vFBL, Normal = nBottom };
            cubeMesh.Vertices[17] = new Vertex { Coordinates = vFBR, Normal = nBottom };
            cubeMesh.Vertices[18] = new Vertex { Coordinates = vBBR, Normal = nBottom };
            cubeMesh.Vertices[19] = new Vertex { Coordinates = vBBL, Normal = nBottom };

            // Top Wall
            cubeMesh.Vertices[20] = new Vertex { Coordinates = vBTL, Normal = nTop };
            cubeMesh.Vertices[21] = new Vertex { Coordinates = vBTR, Normal = nTop };
            cubeMesh.Vertices[22] = new Vertex { Coordinates = vFTR, Normal = nTop };
            cubeMesh.Vertices[23] = new Vertex { Coordinates = vFTL, Normal = nTop };

            // Counter-clockwise vertices order indicate front side.
            cubeMesh.Faces[0] = new Face { A = 0, B = 3, C = 1 }; // Wall Front
            cubeMesh.Faces[1] = new Face { A = 1, B = 3, C = 2 };
            cubeMesh.Faces[2] = new Face { A = 4, B = 7, C = 5 }; // Wall Right
            cubeMesh.Faces[3] = new Face { A = 5, B = 7, C = 6 };
            cubeMesh.Faces[4] = new Face { A = 9, B = 10, C = 8 }; // Wall Back
            cubeMesh.Faces[5] = new Face { A = 8, B = 10, C = 11 };
            cubeMesh.Faces[6] = new Face { A = 12, B = 15, C = 13 }; // Wall Left
            cubeMesh.Faces[7] = new Face { A = 13, B = 15, C = 14 };
            cubeMesh.Faces[8] = new Face { A = 16, B = 19, C = 17 }; // Wall Bottom
            cubeMesh.Faces[9] = new Face { A = 17, B = 19, C = 18 };
            cubeMesh.Faces[10] = new Face { A = 20, B = 23, C = 21 }; // Wall Top
            cubeMesh.Faces[11] = new Face { A = 21, B = 23, C = 22 };

            return cubeMesh;
        }

        public static Mesh ConstructSphere()
        {
            // Sphere construction implemented as mentioned in suggestions to Task 5 (section 2.1.3).

            float r = 1.0f; // Radius.
            int m = 20;  // Number of circles of latitude.
            int n = 20;  // Number of regular angles.

            int vCount = m * n + 2;
            int fCount = 2 * m * n;

            Mesh sphereMesh = new Mesh("Sphere", vCount, fCount);

            // Setup North and South pole vertices.
            sphereMesh.Vertices[0] = new Vertex { Coordinates = new Vector3(0, r, 0) };
            sphereMesh.Vertices[vCount - 1] = new Vertex { Coordinates = new Vector3(0, -r, 0) };

            // Setup remaining vertices.
            for (int i = 1; i <= m; i++)
            {
                for (int j = 1; j <= n; j++)
                {
                    int ind = (i - 1) * n + j;

                    float theta = 2 * (float)Math.PI / n * (j - 1);
                    float phi = (float)Math.PI / (m + 1) * i;

                    float x = r * (float)Math.Cos(theta) * (float)Math.Sin(phi);
                    float y = r * (float)Math.Cos(phi);
                    float z = r * (float)Math.Sin(theta) * (float)Math.Sin(phi);
                    Vector3 coords = new Vector3(x, y, z);

                    sphereMesh.Vertices[ind] = new Vertex { Coordinates = coords };
                }
            }

            // Setup normals. Since sphere origin is 0,0,0 normal is simply vertex position
            // (divided by r to make unit length).
            for (int i = 0; i < vCount; i++)
            {
                Vector3 normal = new Vector3(
                    sphereMesh.Vertices[i].Coordinates.X / r,
                    sphereMesh.Vertices[i].Coordinates.Y / r,
                    sphereMesh.Vertices[i].Coordinates.Z / r);
                sphereMesh.Vertices[i].Normal = normal;
            }

            // Setup triangles (North pole).
            for (int i = 0; i <= n - 2; i++)
            {
                sphereMesh.Faces[i] = new Face { A = 0, B = i + 2, C = i + 1 };
            }

            // Setup triangles (South pole).
            for (int i = 0; i <= n - 2; i++)
            {
                int ind = 2 * (m - 1) * (n) + i;
                sphereMesh.Faces[ind] = new Face { A = m * n + 1, B = (m - 1) * n + i + 1, C = (m - 1) * n + i + 2 };
            }

            int indLast = 2 * (m - 1) * (n) + n - 1;
            sphereMesh.Faces[indLast] = new Face { A = m * n + 1, B = m * n, C = (m - 1) * n + 1 };

            sphereMesh.Faces[n - 1] = new Face { A = 0, B = 1, C = n };

            // Setup trianlges (between North and South poles).
            for (int i = 0; i <= m - 2; i++)
            {
                //for (int j = 1; j <= n-1; j++)
                for (int j = 1; j <= n; j++)
                {
                    int ind1 = (2 * i + 1) * n + j - 1;
                    int ind2 = (2 * i + 2) * n + j - 1;

                    if (j == n)
                    {
                        sphereMesh.Faces[ind1] = new Face { A = (i + 1) * n, B = i * n + 1, C = (i + 1) * n + 1 };
                        sphereMesh.Faces[ind2] = new Face { A = (i + 1) * n, B = (i + 1) * n + 1, C = (i + 2) * n };
                    }
                    else
                    {
                        sphereMesh.Faces[ind1] = new Face { A = i * n + j, B = i * n + j + 1, C = (i + 1) * n + j + 1 };
                        sphereMesh.Faces[ind2] = new Face { A = i * n + j, B = (i + 1) * n + j + 1, C = (i + 1) * n + j };
                    }
                }
            }

            return sphereMesh;
        }
    }
}
